<!DOCTYPE html>
<html lang="zh-CN">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <meta name="robots" content="noodp" />
        <title>二、多线程冲突了怎么办 - L_B__</title><meta name="referrer" content="no-referrer">
<meta name="description" content="多线程冲突了怎么办"><meta property="og:title" content="二、多线程冲突了怎么办" />
<meta property="og:description" content="多线程冲突了怎么办" />
<meta property="og:type" content="article" />
<meta property="og:url" content="https://acking-you.github.io/posts/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%B2%E7%AA%81%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E/" /><meta property="og:image" content="https://acking-you.github.io/logo.png"/><meta property="article:section" content="posts" />
<meta property="article:published_time" content="2023-05-27T00:00:00+00:00" />
<meta property="article:modified_time" content="2023-05-27T00:00:00+00:00" />

<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:image" content="https://acking-you.github.io/logo.png"/>

<meta name="twitter:title" content="二、多线程冲突了怎么办"/>
<meta name="twitter:description" content="多线程冲突了怎么办"/>
<meta name="application-name" content="FeelIt">
<meta name="apple-mobile-web-app-title" content="FeelIt"><meta name="theme-color" content="#ffffff"><meta name="msapplication-TileColor" content="#da532c"><link rel="canonical" href="https://acking-you.github.io/posts/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%B2%E7%AA%81%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E/" /><link rel="prev" href="https://acking-you.github.io/posts/lab0-%E5%AE%9E%E7%8E%B0bytestream/" /><link rel="next" href="https://acking-you.github.io/posts/%E4%BA%8Cmysql%E4%B8%80%E6%9D%A1%E8%AE%B0%E5%BD%95%E6%80%8E%E4%B9%88%E5%AD%98%E5%82%A8%E7%9A%84/" /><link rel="stylesheet" href="/css/page.min.css"><link rel="stylesheet" href="/css/home.min.css"><script type="application/ld+json">
    {
        "@context": "http://schema.org",
        "@type": "BlogPosting",
        "headline": "二、多线程冲突了怎么办",
        "inLanguage": "zh-CN",
        "mainEntityOfPage": {
            "@type": "WebPage",
            "@id": "https:\/\/acking-you.github.io\/posts\/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%B2%E7%AA%81%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E\/"
        },"genre": "posts","keywords": "多线程冲突了怎么办","wordcount":  8403 ,
        "url": "https:\/\/acking-you.github.io\/posts\/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%B2%E7%AA%81%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E\/","datePublished": "2023-05-27T00:00:00+00:00","dateModified": "2023-05-27T00:00:00+00:00","publisher": {
            "@type": "Organization",
            "name": "作者"},"author": {
                "@type": "Person",
                "name": "作者"
            },"description": "多线程冲突了怎么办"
    }
    </script></head><body data-header-desktop="auto" data-header-mobile="auto"><script>(window.localStorage && localStorage.getItem('theme') ? localStorage.getItem('theme') === 'dark' : ('auto' === 'auto' ? window.matchMedia('(prefers-color-scheme: dark)').matches : 'auto' === 'dark')) && document.body.setAttribute('theme', 'dark');</script>

        <div id="mask"></div><div class="wrapper"><header class="desktop" id="header-desktop">
    <div class="header-wrapper">
        <div class="header-title">
            <a href="/" title="L_B__">L_B__</a>
        </div>
        <div class="menu">
            <div class="menu-inner"><a class="menu-item" href="/posts/"> 文章 </a><a class="menu-item" href="/tags/"> 标签 </a><a class="menu-item" href="/categories/"> 分类 </a><span class="menu-item delimiter"></span><span class="menu-item search" id="search-desktop">
                        <input type="text" placeholder="搜索文章标题或内容..." id="search-input-desktop">
                        <a href="#" class="search-button search-toggle" id="search-toggle-desktop" title="搜索">
                            <i class="fas fa-search fa-fw"></i>
                        </a>
                        <a href="#" class="search-button search-clear" id="search-clear-desktop" title="清空">
                            <i class="fas fa-times-circle fa-fw"></i>
                        </a>
                        <span class="search-button search-loading" id="search-loading-desktop">
                            <i class="fas fa-spinner fa-fw fa-spin"></i>
                        </span>
                    </span><a href="javascript:void(0);" class="menu-item theme-switch" title="切换主题">
                    <i class="fas fa-adjust fa-fw"></i>
                </a>
            </div>
        </div>
    </div>
</header><header class="mobile" id="header-mobile">
    <div class="header-container">
        <div class="header-wrapper">
            <div class="header-title">
                <a href="/" title="L_B__">L_B__</a>
            </div>
            <div class="menu-toggle" id="menu-toggle-mobile">
                <span></span><span></span><span></span>
            </div>
        </div>
        <div class="menu" id="menu-mobile"><div class="search-wrapper">
                    <div class="search mobile" id="search-mobile">
                        <input type="text" placeholder="搜索文章标题或内容..." id="search-input-mobile">
                        <a href="#" class="search-button search-toggle" id="search-toggle-mobile" title="搜索">
                            <i class="fas fa-search fa-fw"></i>
                        </a>
                        <a href="#" class="search-button search-clear" id="search-clear-mobile" title="清空">
                            <i class="fas fa-times-circle fa-fw"></i>
                        </a>
                        <span class="search-button search-loading" id="search-loading-mobile">
                            <i class="fas fa-spinner fa-fw fa-spin"></i>
                        </span>
                    </div>
                    <a href="#" class="search-cancel" id="search-cancel-mobile">
                        取消
                    </a>
                </div><a class="menu-item" href="/posts/" title="">文章</a><a class="menu-item" href="/tags/" title="">标签</a><a class="menu-item" href="/categories/" title="">分类</a><div class="menu-item"><a href="javascript:void(0);" class="theme-switch" title="切换主题">
                    <i class="fas fa-adjust fa-fw"></i>
                </a>
            </div></div>
    </div>
</header><div class="search-dropdown desktop">
    <div id="search-dropdown-desktop"></div>
</div>
<div class="search-dropdown mobile">
    <div id="search-dropdown-mobile"></div>
</div><main class="main">
                <div class="container"><div class="toc" id="toc-auto">
            <h2 class="toc-title">目录</h2>
            <div class="toc-content" id="toc-content-auto"></div>
        </div><article class="page single" data-toc="disable"><div class="featured-image"><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://img-blog.csdnimg.cn/img_convert/c592952aec4e1c875073f2a3d970cde5.png#pic_center"
        data-srcset="https://img-blog.csdnimg.cn/img_convert/c592952aec4e1c875073f2a3d970cde5.png#pic_center, https://img-blog.csdnimg.cn/img_convert/c592952aec4e1c875073f2a3d970cde5.png#pic_center 1.5x, https://img-blog.csdnimg.cn/img_convert/c592952aec4e1c875073f2a3d970cde5.png#pic_center 2x"
        data-sizes="auto"
        alt="https://img-blog.csdnimg.cn/img_convert/c592952aec4e1c875073f2a3d970cde5.png#pic_center"
        title="多线程冲突了怎么办" /></div><div class="single-card" data-image="true"><h2 class="single-title animated flipInX">二、多线程冲突了怎么办</h2><div class="post-meta">
                <div class="post-meta-line"><span class="post-author"><a href="/" title="Author" rel=" author" class="author"><i class="fas fa-user-circle fa-fw"></i>作者</a></span>&nbsp;<span class="post-category">出版于  <a href="/categories/%E8%BF%9B%E7%A8%8B%E7%AE%A1%E7%90%86/"><i class="far fa-folder fa-fw"></i>进程管理</a></span></div>
                <div class="post-meta-line"><span><i class="far fa-calendar-alt fa-fw"></i>&nbsp;<time datetime="2023-05-27">2023-05-27</time></span>&nbsp;<span><i class="fas fa-pencil-alt fa-fw"></i>&nbsp;约 8403 字</span>&nbsp;
                    <span><i class="far fa-clock fa-fw"></i>&nbsp;预计阅读 17 分钟</span>&nbsp;</div>
            </div>
            
            <hr><div class="details toc" id="toc-static"  data-kept="">
                    <div class="details-summary toc-title">
                        <span>目录</span>
                        <span><i class="details-icon fas fa-angle-right"></i></span>
                    </div>
                    <div class="details-content toc-content" id="toc-content-static"><nav id="TableOfContents">
  <ul>
    <li><a href="#httpsxiaolincodingcomos4_processmultithread_synchtml竞争与协作竞争与协作"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%E7%AB%9E%E4%BA%89%E4%B8%8E%E5%8D%8F%E4%BD%9C">#</a>竞争与协作</a>
      <ul>
        <li><a href="#httpsxiaolincodingcomos4_processmultithread_synchtml互斥的概念互斥的概念"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%E4%BA%92%E6%96%A5%E7%9A%84%E6%A6%82%E5%BF%B5">#</a>互斥的概念</a></li>
        <li><a href="#httpsxiaolincodingcomos4_processmultithread_synchtml同步的概念同步的概念"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%E5%90%8C%E6%AD%A5%E7%9A%84%E6%A6%82%E5%BF%B5">#</a>同步的概念</a></li>
      </ul>
    </li>
    <li><a href="#httpsxiaolincodingcomos4_processmultithread_synchtml互斥与同步的实现和使用互斥与同步的实现和使用"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5%E7%9A%84%E5%AE%9E%E7%8E%B0%E5%92%8C%E4%BD%BF%E7%94%A8">#</a>互斥与同步的实现和使用</a>
      <ul>
        <li><a href="#httpsxiaolincodingcomos4_processmultithread_synchtml锁锁"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%E9%94%81">#</a>锁</a></li>
        <li><a href="#httpsxiaolincodingcomos4_processmultithread_synchtml信号量信号量"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%E4%BF%A1%E5%8F%B7%E9%87%8F">#</a>信号量</a></li>
        <li><a href="#httpsxiaolincodingcomos4_processmultithread_synchtml生产者-消费者问题生产者-消费者问题"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%E7%94%9F%E4%BA%A7%E8%80%85-%E6%B6%88%E8%B4%B9%E8%80%85%E9%97%AE%E9%A2%98">#</a>生产者-消费者问题</a></li>
      </ul>
    </li>
    <li><a href="#httpsxiaolincodingcomos4_processmultithread_synchtml经典同步问题经典同步问题"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%E7%BB%8F%E5%85%B8%E5%90%8C%E6%AD%A5%E9%97%AE%E9%A2%98">#</a>经典同步问题</a>
      <ul>
        <li><a href="#httpsxiaolincodingcomos4_processmultithread_synchtml哲学家就餐问题哲学家就餐问题"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%E5%93%B2%E5%AD%A6%E5%AE%B6%E5%B0%B1%E9%A4%90%E9%97%AE%E9%A2%98">#</a>哲学家就餐问题</a></li>
        <li><a href="#httpsxiaolincodingcomos4_processmultithread_synchtml读者-写者问题读者-写者问题"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%E8%AF%BB%E8%80%85-%E5%86%99%E8%80%85%E9%97%AE%E9%A2%98">#</a>读者-写者问题</a></li>
      </ul>
    </li>
  </ul>
</nav></div>
                </div><div class="content" id="content"><h1 id="多线程冲突了怎么办">多线程冲突了怎么办？</h1>
<blockquote>
<p>先来看看虚构的小故事</p>
</blockquote>
<p>已经晚上 11 点了，程序员小明的双手还在键盘上飞舞着，眼神依然注视着的电脑屏幕。</p>
<p>没办法这段时间公司业绩增长中，需求自然也多了起来，加班自然也少不了。</p>
<p>天气变化莫测，这时窗外下起了蓬勃大雨，同时闪电轰鸣。</p>
<p>但这一丝都没有影响到小明，始料未及，突然一道巨大的雷一闪而过，办公楼就这么停电了，随后整栋楼都在回荡着的小明那一声撕心裂肺的「卧槽」。</p>
<p>此时，求小明的心里面积有多大？</p>
<p>等小明心里平复后，突然肚子非常的痛，想上厕所，小明心想肯定是晚上吃的某堡王有问题。</p>
<p>整栋楼都停了电，小明两眼一抹黑，啥都看不见，只能靠摸墙的方法，一步一步的来到了厕所门口。</p>
<p>到了厕所（<strong>共享资源</strong>），由于实在太急，小明直接冲入了厕所里，用手摸索着刚好第一个门没锁门，便夺门而入。</p>
<p>这就荒唐了，这个门里面正好小红在上着厕所，正好这个厕所门是坏了的，没办法锁门。</p>
<p>黑暗中，小红虽然看不见，但靠着声音，发现自己面前的这扇门有动静，觉得不对劲，于是铆足了力气，用她穿着高跟鞋脚，用力地一脚踢了过去。</p>
<p>小明很幸运，被踢中了「命根子」，撕心裂肺地喊出了一个字「痛」！</p>
<p>故事说完了，扯了那么多，实际上是为了说明，<strong>对于共享资源，如果没有上锁，在多线程的环境里，那么就可能会发生翻车现场。</strong></p>
<p>接下来，用 <code>30+</code> 张图，带大家走进操作系统中避免多线程资源竞争的<strong>互斥、同步</strong>的方法。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/2-%E6%8F%90%E7%BA%B2.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/2-%E6%8F%90%E7%BA%B2.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/2-%E6%8F%90%E7%BA%B2.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/2-%E6%8F%90%E7%BA%B2.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/2-%E6%8F%90%E7%BA%B2.jpg"
        title="img" /></p>
<hr>
<h2 id="httpsxiaolincodingcomos4_processmultithread_synchtml竞争与协作竞争与协作"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%e7%ab%9e%e4%ba%89%e4%b8%8e%e5%8d%8f%e4%bd%9c" target="_blank" rel="noopener noreffer">#</a>竞争与协作</h2>
<p>在单核 CPU 系统里，为了实现多个程序同时运行的假象，操作系统通常以时间片调度的方式，让每个进程执行每次执行一个时间片，时间片用完了，就切换下一个进程运行，由于这个时间片的时间很短，于是就造成了「并发」的现象。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/3-%E5%B9%B6%E5%8F%91.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/3-%E5%B9%B6%E5%8F%91.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/3-%E5%B9%B6%E5%8F%91.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/3-%E5%B9%B6%E5%8F%91.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/3-%E5%B9%B6%E5%8F%91.jpg"
        title="并发" /></p>
<p>另外，操作系统也为每个进程创建巨大、私有的虚拟内存的假象，这种地址空间的抽象让每个程序好像拥有自己的内存，而实际上操作系统在背后秘密地让多个地址空间「复用」物理内存或者磁盘。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/4-%E5%86%85%E5%AD%98%E4%BA%A4%E6%8D%A2.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/4-%E5%86%85%E5%AD%98%E4%BA%A4%E6%8D%A2.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/4-%E5%86%85%E5%AD%98%E4%BA%A4%E6%8D%A2.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/4-%E5%86%85%E5%AD%98%E4%BA%A4%E6%8D%A2.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/4-%E5%86%85%E5%AD%98%E4%BA%A4%E6%8D%A2.jpg"
        title="虚拟内存管理-换入换出" /></p>
<p>如果一个程序只有一个执行流程，也代表它是单线程的。当然一个程序可以有多个执行流程，也就是所谓的多线程程序，线程是调度的基本单位，进程则是资源分配的基本单位。</p>
<p>所以，线程之间是可以共享进程的资源，比如代码段、堆空间、数据段、打开的文件等资源，但每个线程都有自己独立的栈空间。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/5-%E5%A4%9A%E7%BA%BF%E7%A8%8B.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/5-%E5%A4%9A%E7%BA%BF%E7%A8%8B.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/5-%E5%A4%9A%E7%BA%BF%E7%A8%8B.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/5-%E5%A4%9A%E7%BA%BF%E7%A8%8B.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/5-%E5%A4%9A%E7%BA%BF%E7%A8%8B.jpg"
        title="多线程" /></p>
<p>那么问题就来了，多个线程如果竞争共享资源，如果不采取有效的措施，则会造成共享数据的混乱。</p>
<p>我们做个小实验，创建两个线程，它们分别对共享变量 <code>i</code> 自增 <code>1</code> 执行 <code>10000</code> 次，如下代码（虽然说是 C++ 代码，但是没学过 C++ 的同学也是看到懂的）：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/6-%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AB%9E%E4%BA%89C%2B%2B%E4%BB%A3%E7%A0%81%E4%BE%8B%E5%AD%90.jpeg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/6-%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AB%9E%E4%BA%89C%2B%2B%E4%BB%A3%E7%A0%81%E4%BE%8B%E5%AD%90.jpeg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/6-%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AB%9E%E4%BA%89C%2B%2B%E4%BB%A3%E7%A0%81%E4%BE%8B%E5%AD%90.jpeg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/6-%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AB%9E%E4%BA%89C%2B%2B%E4%BB%A3%E7%A0%81%E4%BE%8B%E5%AD%90.jpeg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/6-%E5%A4%9A%E7%BA%BF%E7%A8%8B%E7%AB%9E%E4%BA%89C%2B%2B%E4%BB%A3%E7%A0%81%E4%BE%8B%E5%AD%90.jpeg"
        title="img" /></p>
<p>按理来说，<code>i</code> 变量最后的值应该是 <code>20000</code>，但很不幸，并不是如此。我们对上面的程序执行一下：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/7-%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/7-%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/7-%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/7-%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/7-%E8%BF%90%E8%A1%8C%E7%BB%93%E6%9E%9C.jpg"
        title="img" /></p>
<p>运行了两次，发现出现了 <code>i</code> 值的结果是 <code>15173</code>，也会出现 <code>20000</code> 的 i 值结果。</p>
<p>每次运行不但会产生错误，而且得到不同的结果。在计算机里是不能容忍的，虽然是小概率出现的错误，但是小概率事件它一定是会发生的，「墨菲定律」大家都懂吧。</p>
<blockquote>
<p>为什么会发生这种情况？</p>
</blockquote>
<p>为了理解为什么会发生这种情况，我们必须了解编译器为更新计数器 <code>i</code> 变量生成的代码序列，也就是要了解汇编指令的执行顺序。</p>
<p>在这个例子中，我们只是想给 <code>i</code> 加上数字 1，那么它对应的汇编指令执行过程是这样的：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/8-%E6%B1%87%E7%BC%96%E8%AF%AD%E5%8F%A5%E8%B5%8B%E5%80%BC%E8%BF%87%E7%A8%8B.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/8-%E6%B1%87%E7%BC%96%E8%AF%AD%E5%8F%A5%E8%B5%8B%E5%80%BC%E8%BF%87%E7%A8%8B.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/8-%E6%B1%87%E7%BC%96%E8%AF%AD%E5%8F%A5%E8%B5%8B%E5%80%BC%E8%BF%87%E7%A8%8B.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/8-%E6%B1%87%E7%BC%96%E8%AF%AD%E5%8F%A5%E8%B5%8B%E5%80%BC%E8%BF%87%E7%A8%8B.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/8-%E6%B1%87%E7%BC%96%E8%AF%AD%E5%8F%A5%E8%B5%8B%E5%80%BC%E8%BF%87%E7%A8%8B.jpg"
        title="img" /></p>
<p>可以发现，只是单纯给 <code>i</code> 加上数字 1，在 CPU 运行的时候，实际上要执行 <code>3</code> 条指令。</p>
<p>设想我们的线程 1 进入这个代码区域，它将 i 的值（假设此时是 50 ）从内存加载到它的寄存器中，然后它向寄存器加 1，此时在寄存器中的 i 值是 51。</p>
<p>现在，一件不幸的事情发生了：<strong>时钟中断发生</strong>。因此，操作系统将当前正在运行的线程的状态保存到线程的线程控制块 TCB。</p>
<p>现在更糟的事情发生了，线程 2 被调度运行，并进入同一段代码。它也执行了第一条指令，从内存获取 i 值并将其放入到寄存器中，此时内存中 i 的值仍为 50，因此线程 2 寄存器中的 i 值也是 50。假设线程 2 执行接下来的两条指令，将寄存器中的 i 值 + 1，然后将寄存器中的 i 值保存到内存中，于是此时全局变量 i 值是 51。</p>
<p>最后，又发生一次上下文切换，线程 1 恢复执行。还记得它已经执行了两条汇编指令，现在准备执行最后一条指令。回忆一下， 线程 1 寄存器中的 i 值是51，因此，执行最后一条指令后，将值保存到内存，全局变量 i 的值再次被设置为 51。</p>
<p>简单来说，增加 i （值为 50 ）的代码被运行两次，按理来说，最后的 i 值应该是 52，但是由于<strong>不可控的调度</strong>，导致最后 i 值却是 51。</p>
<p>针对上面线程 1 和线程 2 的执行过程，我画了一张流程图，会更明确一些：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/9-%E6%B1%87%E7%BC%96%E8%AF%AD%E5%8F%A5-%E8%B5%8B%E5%80%BC%E8%BF%87%E7%A8%8B-%E7%AB%9E%E4%BA%89.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/9-%E6%B1%87%E7%BC%96%E8%AF%AD%E5%8F%A5-%E8%B5%8B%E5%80%BC%E8%BF%87%E7%A8%8B-%E7%AB%9E%E4%BA%89.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/9-%E6%B1%87%E7%BC%96%E8%AF%AD%E5%8F%A5-%E8%B5%8B%E5%80%BC%E8%BF%87%E7%A8%8B-%E7%AB%9E%E4%BA%89.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/9-%E6%B1%87%E7%BC%96%E8%AF%AD%E5%8F%A5-%E8%B5%8B%E5%80%BC%E8%BF%87%E7%A8%8B-%E7%AB%9E%E4%BA%89.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/9-%E6%B1%87%E7%BC%96%E8%AF%AD%E5%8F%A5-%E8%B5%8B%E5%80%BC%E8%BF%87%E7%A8%8B-%E7%AB%9E%E4%BA%89.jpg"
        title="蓝色表示线程 1 ，红色表示线程 2" /></p>
<h3 id="httpsxiaolincodingcomos4_processmultithread_synchtml互斥的概念互斥的概念"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%e4%ba%92%e6%96%a5%e7%9a%84%e6%a6%82%e5%bf%b5" target="_blank" rel="noopener noreffer">#</a>互斥的概念</h3>
<p>上面展示的情况称为<strong>竞争条件（*race condition*）</strong>，当多线程相互竞争操作共享变量时，由于运气不好，即在执行过程中发生了上下文切换，我们得到了错误的结果，事实上，每次运行都可能得到不同的结果，因此输出的结果存在<strong>不确定性（*indeterminate*）</strong>。</p>
<p>由于多线程执行操作共享变量的这段代码可能会导致竞争状态，因此我们将此段代码称为<strong>临界区（*critical section*），它是访问共享资源的代码片段，一定不能给多线程同时执行。</strong></p>
<p>我们希望这段代码是<strong>互斥（*mutualexclusion*）的，也就说保证一个线程在临界区执行时，其他线程应该被阻止进入临界区</strong>，说白了，就是这段代码执行过程中，最多只能出现一个线程。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/10-%E4%B8%B4%E7%95%8C%E5%8C%BA.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/10-%E4%B8%B4%E7%95%8C%E5%8C%BA.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/10-%E4%B8%B4%E7%95%8C%E5%8C%BA.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/10-%E4%B8%B4%E7%95%8C%E5%8C%BA.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/10-%E4%B8%B4%E7%95%8C%E5%8C%BA.jpg"
        title="互斥" /></p>
<p>另外，说一下互斥也并不是只针对多线程。在多进程竞争共享资源的时候，也同样是可以使用互斥的方式来避免资源竞争造成的资源混乱。</p>
<h3 id="httpsxiaolincodingcomos4_processmultithread_synchtml同步的概念同步的概念"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%e5%90%8c%e6%ad%a5%e7%9a%84%e6%a6%82%e5%bf%b5" target="_blank" rel="noopener noreffer">#</a>同步的概念</h3>
<p>互斥解决了并发进程/线程对临界区的使用问题。这种基于临界区控制的交互作用是比较简单的，只要一个进程/线程进入了临界区，其他试图想进入临界区的进程/线程都会被阻塞着，直到第一个进程/线程离开了临界区。</p>
<p>我们都知道在多线程里，每个线程并不一定是顺序执行的，它们基本是以各自独立的、不可预知的速度向前推进，但有时候我们又希望多个线程能密切合作，以实现一个共同的任务。</p>
<p>例子，线程 1 是负责读入数据的，而线程 2 是负责处理数据的，这两个线程是相互合作、相互依赖的。线程 2 在没有收到线程 1 的唤醒通知时，就会一直阻塞等待，当线程 1 读完数据需要把数据传给线程 2 时，线程 1 会唤醒线程 2，并把数据交给线程 2 处理。</p>
<p><strong>所谓同步，就是并发进程/线程在一些关键点上可能需要互相等待与互通消息，这种相互制约的等待与互通信息称为进程/线程同步</strong>。</p>
<p>举个生活的同步例子，你肚子饿了想要吃饭，你叫妈妈早点做菜，妈妈听到后就开始做菜，但是在妈妈没有做完饭之前，你必须阻塞等待，等妈妈做完饭后，自然会通知你，接着你吃饭的事情就可以进行了。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/11-%E5%90%83%E9%A5%AD%E5%90%8C%E6%AD%A5.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/11-%E5%90%83%E9%A5%AD%E5%90%8C%E6%AD%A5.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/11-%E5%90%83%E9%A5%AD%E5%90%8C%E6%AD%A5.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/11-%E5%90%83%E9%A5%AD%E5%90%8C%E6%AD%A5.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/11-%E5%90%83%E9%A5%AD%E5%90%8C%E6%AD%A5.jpg"
        title="吃饭与做菜的同步关系" /></p>
<p>注意，同步与互斥是两种不同的概念：</p>
<ul>
<li>同步就好比：「操作 A 应在操作 B 之前执行」，「操作 C 必须在操作 A 和操作 B 都完成之后才能执行」等；</li>
<li>互斥就好比：「操作 A 和操作 B 不能在同一时刻执行」；</li>
</ul>
<hr>
<h2 id="httpsxiaolincodingcomos4_processmultithread_synchtml互斥与同步的实现和使用互斥与同步的实现和使用"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%e4%ba%92%e6%96%a5%e4%b8%8e%e5%90%8c%e6%ad%a5%e7%9a%84%e5%ae%9e%e7%8e%b0%e5%92%8c%e4%bd%bf%e7%94%a8" target="_blank" rel="noopener noreffer">#</a>互斥与同步的实现和使用</h2>
<p>在进程/线程并发执行的过程中，进程/线程之间存在协作的关系，例如有互斥、同步的关系。</p>
<p>为了实现进程/线程间正确的协作，操作系统必须提供实现进程协作的措施和方法，主要的方法有两种：</p>
<ul>
<li><em>锁</em>：加锁、解锁操作；</li>
<li><em>信号量</em>：P、V 操作；</li>
</ul>
<p>这两个都可以方便地实现进程/线程互斥，而信号量比锁的功能更强一些，它还可以方便地实现进程/线程同步。</p>
<h3 id="httpsxiaolincodingcomos4_processmultithread_synchtml锁锁"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%e9%94%81" target="_blank" rel="noopener noreffer">#</a>锁</h3>
<p>使用加锁操作和解锁操作可以解决并发线程/进程的互斥问题。</p>
<p>任何想进入临界区的线程，必须先执行加锁操作。若加锁操作顺利通过，则线程可进入临界区；在完成对临界资源的访问后再执行解锁操作，以释放该临界资源。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/12-%E4%BA%92%E6%96%A5%E9%94%81.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/12-%E4%BA%92%E6%96%A5%E9%94%81.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/12-%E4%BA%92%E6%96%A5%E9%94%81.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/12-%E4%BA%92%E6%96%A5%E9%94%81.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/12-%E4%BA%92%E6%96%A5%E9%94%81.jpg"
        title="加锁-解锁" /></p>
<p>根据锁的实现不同，可以分为「忙等待锁」和「无忙等待锁」。</p>
<blockquote>
<p>我们先来看看「忙等待锁」的实现</p>
</blockquote>
<p>在说明「忙等待锁」的实现之前，先介绍现代 CPU 体系结构提供的特殊<strong>原子操作指令 —— 测试和置位（*Test-and-Set*）指令</strong>。</p>
<p>如果用 C 代码表示 Test-and-Set 指令，形式如下：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/13-TestAndSet.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/13-TestAndSet.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/13-TestAndSet.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/13-TestAndSet.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/13-TestAndSet.jpg"
        title="img" /></p>
<p>测试并设置指令做了下述事情:</p>
<ul>
<li>把 <code>old_ptr</code> 更新为 <code>new</code> 的新值</li>
<li>返回 <code>old_ptr</code> 的旧值；</li>
</ul>
<p>当然，<strong>关键是这些代码是原子执行</strong>。因为既可以测试旧值，又可以设置新值，所以我们把这条指令叫作「测试并设置」。</p>
<p>那什么是原子操作呢？<strong>原子操作就是要么全部执行，要么都不执行，不能出现执行到一半的中间状态</strong></p>
<p>我们可以运用 Test-and-Set 指令来实现「忙等待锁」，代码如下：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/14-%E8%87%AA%E6%97%8B%E9%94%81.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/14-%E8%87%AA%E6%97%8B%E9%94%81.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/14-%E8%87%AA%E6%97%8B%E9%94%81.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/14-%E8%87%AA%E6%97%8B%E9%94%81.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/14-%E8%87%AA%E6%97%8B%E9%94%81.jpg"
        title="img" /></p>
<p>我们来确保理解为什么这个锁能工作：</p>
<ul>
<li>第一个场景是，首先假设一个线程在运行，调用 <code>lock()</code>，没有其他线程持有锁，所以 <code>flag</code> 是 0。当调用 <code>TestAndSet(flag, 1)</code> 方法，返回 0，线程会跳出 while 循环，获取锁。同时也会原子的设置 flag 为1，标志锁已经被持有。当线程离开临界区，调用 <code>unlock()</code> 将 <code>flag</code> 清理为 0。</li>
<li>第二种场景是，当某一个线程已经持有锁（即 <code>flag</code> 为1）。本线程调用 <code>lock()</code>，然后调用 <code>TestAndSet(flag, 1)</code>，这一次返回 1。只要另一个线程一直持有锁，<code>TestAndSet()</code> 会重复返回 1，本线程会一直<strong>忙等</strong>。当 <code>flag</code> 终于被改为 0，本线程会调用 <code>TestAndSet()</code>，返回 0 并且原子地设置为 1，从而获得锁，进入临界区。</li>
</ul>
<p>很明显，当获取不到锁时，线程就会一直 while 循环，不做任何事情，所以就被称为「忙等待锁」，也被称为<strong>自旋锁（*spin lock*）</strong>。</p>
<p>这是最简单的一种锁，一直自旋，利用 CPU 周期，直到锁可用。在单处理器上，需要抢占式的调度器（即不断通过时钟中断一个线程，运行其他线程）。否则，自旋锁在单 CPU 上无法使用，因为一个自旋的线程永远不会放弃 CPU。</p>
<blockquote>
<p>再来看看「无等待锁」的实现</p>
</blockquote>
<p>无等待锁顾明思议就是获取不到锁的时候，不用自旋。</p>
<p>既然不想自旋，那当没获取到锁的时候，就把当前线程放入到锁的等待队列，然后执行调度程序，把 CPU 让给其他线程执行。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/15-%E6%97%A0%E7%AD%89%E5%BE%85%E9%94%81.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/15-%E6%97%A0%E7%AD%89%E5%BE%85%E9%94%81.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/15-%E6%97%A0%E7%AD%89%E5%BE%85%E9%94%81.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/15-%E6%97%A0%E7%AD%89%E5%BE%85%E9%94%81.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/15-%E6%97%A0%E7%AD%89%E5%BE%85%E9%94%81.jpg"
        title="img" /></p>
<p>本次只是提出了两种简单锁的实现方式。当然，在具体操作系统实现中，会更复杂，但也离不开本例子两个基本元素。</p>
<p>如果你想要对锁的更进一步理解，推荐大家可以看《操作系统导论》第 28 章锁的内容，这本书在「微信读书」就可以免费看。</p>
<h3 id="httpsxiaolincodingcomos4_processmultithread_synchtml信号量信号量"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%e4%bf%a1%e5%8f%b7%e9%87%8f" target="_blank" rel="noopener noreffer">#</a>信号量</h3>
<p>信号量是操作系统提供的一种协调共享资源访问的方法。</p>
<p>通常<strong>信号量表示资源的数量</strong>，对应的变量是一个整型（<code>sem</code>）变量。</p>
<p>另外，还有<strong>两个原子操作的系统调用函数来控制信号量的</strong>，分别是：</p>
<ul>
<li><em>P 操作</em>：将 <code>sem</code> 减 <code>1</code>，相减后，如果 <code>sem &lt; 0</code>，则进程/线程进入阻塞等待，否则继续，表明 P 操作可能会阻塞；</li>
<li><em>V 操作</em>：将 <code>sem</code> 加 <code>1</code>，相加后，如果 <code>sem &lt;= 0</code>，唤醒一个等待中的进程/线程，表明 V 操作不会阻塞；</li>
</ul>
<p>TIP</p>
<p>很多人问，V 操作 中 sem &lt;= 0 的判断是不是写反了？</p>
<p>没写反，我举个例子，如果 sem = 1，有三个线程进行了 P 操作：</p>
<ul>
<li>第一个线程 P 操作后，sem = 0；</li>
<li>第二个线程 P 操作后，sem = -1；</li>
<li>第三个线程 P 操作后，sem = -2；</li>
</ul>
<p>这时，第一个线程执行 V 操作后， sem 是 -1，因为 sem &lt;= 0，所以要唤醒第二或第三个线程。</p>
<p>P 操作是用在进入临界区之前，V 操作是用在离开临界区之后，这两个操作是必须成对出现的。</p>
<p>举个类比，2 个资源的信号量，相当于 2 条火车轨道，PV 操作如下图过程：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/16-%E7%81%AB%E8%BD%A6PV%E6%93%8D%E4%BD%9C.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/16-%E7%81%AB%E8%BD%A6PV%E6%93%8D%E4%BD%9C.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/16-%E7%81%AB%E8%BD%A6PV%E6%93%8D%E4%BD%9C.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/16-%E7%81%AB%E8%BD%A6PV%E6%93%8D%E4%BD%9C.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/16-%E7%81%AB%E8%BD%A6PV%E6%93%8D%E4%BD%9C.jpg"
        title="信号量与火车轨道" /></p>
<blockquote>
<p>操作系统是如何实现 PV 操作的呢？</p>
</blockquote>
<p>信号量数据结构与 PV 操作的算法描述如下图：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/17-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9FPV%E7%AE%97%E6%B3%95%E6%8F%8F%E8%BF%B0.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/17-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9FPV%E7%AE%97%E6%B3%95%E6%8F%8F%E8%BF%B0.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/17-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9FPV%E7%AE%97%E6%B3%95%E6%8F%8F%E8%BF%B0.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/17-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9FPV%E7%AE%97%E6%B3%95%E6%8F%8F%E8%BF%B0.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/17-%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9FPV%E7%AE%97%E6%B3%95%E6%8F%8F%E8%BF%B0.jpg"
        title="PV 操作的算法描述" /></p>
<p>PV 操作的函数是由操作系统管理和实现的，所以操作系统已经使得执行 PV 函数时是具有原子性的。</p>
<blockquote>
<p>PV 操作如何使用的呢？</p>
</blockquote>
<p>信号量不仅可以实现临界区的互斥访问控制，还可以线程间的事件同步。</p>
<p>我们先来说说如何使用<strong>信号量实现临界区的互斥访问</strong>。</p>
<p>为每类共享资源设置一个信号量 <code>s</code>，其初值为 <code>1</code>，表示该临界资源未被占用。</p>
<p>只要把进入临界区的操作置于 <code>P(s)</code> 和 <code>V(s)</code> 之间，即可实现进程/线程互斥：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/18-%E4%BA%92%E6%96%A5%E4%BF%A1%E5%8F%B7%E9%87%8F.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/18-%E4%BA%92%E6%96%A5%E4%BF%A1%E5%8F%B7%E9%87%8F.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/18-%E4%BA%92%E6%96%A5%E4%BF%A1%E5%8F%B7%E9%87%8F.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/18-%E4%BA%92%E6%96%A5%E4%BF%A1%E5%8F%B7%E9%87%8F.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/18-%E4%BA%92%E6%96%A5%E4%BF%A1%E5%8F%B7%E9%87%8F.jpg"
        title="img" /></p>
<p>此时，任何想进入临界区的线程，必先在互斥信号量上执行 P 操作，在完成对临界资源的访问后再执行 V 操作。由于互斥信号量的初始值为 1，故在第一个线程执行 P 操作后 s 值变为 0，表示临界资源为空闲，可分配给该线程，使之进入临界区。</p>
<p>若此时又有第二个线程想进入临界区，也应先执行 P 操作，结果使 s 变为负值，这就意味着临界资源已被占用，因此，第二个线程被阻塞。</p>
<p>并且，直到第一个线程执行 V 操作，释放临界资源而恢复 s 值为 0 后，才唤醒第二个线程，使之进入临界区，待它完成临界资源的访问后，又执行 V 操作，使 s 恢复到初始值 1。</p>
<p>对于两个并发线程，互斥信号量的值仅取 1、0 和 -1 三个值，分别表示：</p>
<ul>
<li>如果互斥信号量为 1，表示没有线程进入临界区；</li>
<li>如果互斥信号量为 0，表示有一个线程进入临界区；</li>
<li>如果互斥信号量为 -1，表示一个线程进入临界区，另一个线程等待进入。</li>
</ul>
<p>通过互斥信号量的方式，就能保证临界区任何时刻只有一个线程在执行，就达到了互斥的效果。</p>
<p>再来，我们说说如何使用<strong>信号量实现事件同步</strong>。</p>
<p>同步的方式是设置一个信号量，其初值为 <code>0</code>。</p>
<p>我们把前面的「吃饭-做饭」同步的例子，用代码的方式实现一下：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/19-%E4%BA%92%E6%96%A5%E4%BF%A1%E5%8F%B7%E9%87%8F%E5%90%8C%E6%AD%A5%E5%AE%9E%E7%8E%B0-%E5%90%83%E9%A5%AD%E4%BE%8B%E5%AD%90.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/19-%E4%BA%92%E6%96%A5%E4%BF%A1%E5%8F%B7%E9%87%8F%E5%90%8C%E6%AD%A5%E5%AE%9E%E7%8E%B0-%E5%90%83%E9%A5%AD%E4%BE%8B%E5%AD%90.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/19-%E4%BA%92%E6%96%A5%E4%BF%A1%E5%8F%B7%E9%87%8F%E5%90%8C%E6%AD%A5%E5%AE%9E%E7%8E%B0-%E5%90%83%E9%A5%AD%E4%BE%8B%E5%AD%90.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/19-%E4%BA%92%E6%96%A5%E4%BF%A1%E5%8F%B7%E9%87%8F%E5%90%8C%E6%AD%A5%E5%AE%9E%E7%8E%B0-%E5%90%83%E9%A5%AD%E4%BE%8B%E5%AD%90.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/19-%E4%BA%92%E6%96%A5%E4%BF%A1%E5%8F%B7%E9%87%8F%E5%90%8C%E6%AD%A5%E5%AE%9E%E7%8E%B0-%E5%90%83%E9%A5%AD%E4%BE%8B%E5%AD%90.jpg"
        title="img" /></p>
<p>妈妈一开始询问儿子要不要做饭时，执行的是 <code>P(s1)</code> ，相当于询问儿子需不需要吃饭，由于 <code>s1</code> 初始值为 0，此时 <code>s1</code> 变成 -1，表明儿子不需要吃饭，所以妈妈线程就进入等待状态。</p>
<p>当儿子肚子饿时，执行了 <code>V(s1)</code>，使得 <code>s1</code> 信号量从 -1 变成 0，表明此时儿子需要吃饭了，于是就唤醒了阻塞中的妈妈线程，妈妈线程就开始做饭。</p>
<p>接着，儿子线程执行了 <code>P(s2)</code>，相当于询问妈妈饭做完了吗，由于 <code>s2</code> 初始值是 0，则此时 <code>s2</code> 变成 -1，说明妈妈还没做完饭，儿子线程就等待状态。</p>
<p>最后，妈妈终于做完饭了，于是执行 <code>V(s2)</code>，<code>s2</code> 信号量从 -1 变回了 0，于是就唤醒等待中的儿子线程，唤醒后，儿子线程就可以进行吃饭了。</p>
<h3 id="httpsxiaolincodingcomos4_processmultithread_synchtml生产者-消费者问题生产者-消费者问题"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%e7%94%9f%e4%ba%a7%e8%80%85-%e6%b6%88%e8%b4%b9%e8%80%85%e9%97%ae%e9%a2%98" target="_blank" rel="noopener noreffer">#</a>生产者-消费者问题</h3>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/20-%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/20-%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/20-%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/20-%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/20-%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85.jpg"
        title="生产者-消费者模型" /></p>
<p>生产者-消费者问题描述：</p>
<ul>
<li><strong>生产者</strong>在生成数据后，放在一个缓冲区中；</li>
<li><strong>消费者</strong>从缓冲区取出数据处理；</li>
<li>任何时刻，<strong>只能有一个</strong>生产者或消费者可以访问缓冲区；</li>
</ul>
<p>我们对问题分析可以得出：</p>
<ul>
<li>任何时刻只能有一个线程操作缓冲区，说明操作缓冲区是临界代码，<strong>需要互斥</strong>；</li>
<li>缓冲区空时，消费者必须等待生产者生成数据；缓冲区满时，生产者必须等待消费者取出数据。说明生产者和消费者<strong>需要同步</strong>。</li>
</ul>
<p>那么我们需要三个信号量，分别是：</p>
<ul>
<li>互斥信号量 <code>mutex</code>：用于互斥访问缓冲区，初始化值为 1；</li>
<li>资源信号量 <code>fullBuffers</code>：用于消费者询问缓冲区是否有数据，有数据则读取数据，初始化值为 0（表明缓冲区一开始为空）；</li>
<li>资源信号量 <code>emptyBuffers</code>：用于生产者询问缓冲区是否有空位，有空位则生成数据，初始化值为 n （缓冲区大小）；</li>
</ul>
<p>具体的实现代码：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/21-%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/21-%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/21-%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/21-%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/21-%E7%94%9F%E4%BA%A7%E8%80%85%E6%B6%88%E8%B4%B9%E8%80%85%E4%BB%A3%E7%A0%81%E7%A4%BA%E4%BE%8B.jpg"
        title="img" /></p>
<p>如果消费者线程一开始执行 <code>P(fullBuffers)</code>，由于信号量 <code>fullBuffers</code> 初始值为 0，则此时 <code>fullBuffers</code> 的值从 0 变为 -1，说明缓冲区里没有数据，消费者只能等待。</p>
<p>接着，轮到生产者执行 <code>P(emptyBuffers)</code>，表示减少 1 个空槽，如果当前没有其他生产者线程在临界区执行代码，那么该生产者线程就可以把数据放到缓冲区，放完后，执行 <code>V(fullBuffers)</code> ，信号量 <code>fullBuffers</code> 从 -1 变成 0，表明有「消费者」线程正在阻塞等待数据，于是阻塞等待的消费者线程会被唤醒。</p>
<p>消费者线程被唤醒后，如果此时没有其他消费者线程在读数据，那么就可以直接进入临界区，从缓冲区读取数据。最后，离开临界区后，把空槽的个数 + 1。</p>
<hr>
<h2 id="httpsxiaolincodingcomos4_processmultithread_synchtml经典同步问题经典同步问题"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%e7%bb%8f%e5%85%b8%e5%90%8c%e6%ad%a5%e9%97%ae%e9%a2%98" target="_blank" rel="noopener noreffer">#</a>经典同步问题</h2>
<h3 id="httpsxiaolincodingcomos4_processmultithread_synchtml哲学家就餐问题哲学家就餐问题"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%e5%93%b2%e5%ad%a6%e5%ae%b6%e5%b0%b1%e9%a4%90%e9%97%ae%e9%a2%98" target="_blank" rel="noopener noreffer">#</a>哲学家就餐问题</h3>
<p>当初我在校招的时候，面试官也问过「哲学家就餐」这道题目，我当时听的一脸懵逼，无论面试官怎么讲述这个问题，我也始终没听懂，就莫名其妙的说这个问题会「死锁」。</p>
<p>当然，我这回答槽透了，所以当场 game over，残酷又悲惨故事，就不多说了，反正当时菜就是菜。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/22-%E4%B8%8D%E9%9A%BE%E8%BF%87-%E8%A1%A8%E6%83%85.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/22-%E4%B8%8D%E9%9A%BE%E8%BF%87-%E8%A1%A8%E6%83%85.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/22-%E4%B8%8D%E9%9A%BE%E8%BF%87-%E8%A1%A8%E6%83%85.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/22-%E4%B8%8D%E9%9A%BE%E8%BF%87-%E8%A1%A8%E6%83%85.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/22-%E4%B8%8D%E9%9A%BE%E8%BF%87-%E8%A1%A8%E6%83%85.jpg"
        title="img" /></p>
<p>时至今日，看我来图解这道题。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/23-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90%E6%A8%A1%E5%9E%8B.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/23-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90%E6%A8%A1%E5%9E%8B.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/23-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90%E6%A8%A1%E5%9E%8B.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/23-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90%E6%A8%A1%E5%9E%8B.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/23-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90%E6%A8%A1%E5%9E%8B.jpg"
        title="哲学家就餐的问题" /></p>
<p>先来看看哲学家就餐的问题描述：</p>
<ul>
<li><code>5</code> 个老大哥哲学家，闲着没事做，围绕着一张圆桌吃面；</li>
<li>巧就巧在，这个桌子只有 <code>5</code> 支叉子，每两个哲学家之间放一支叉子；</li>
<li>哲学家围在一起先思考，思考中途饿了就会想进餐；</li>
<li><strong>奇葩的是，这些哲学家要两支叉子才愿意吃面，也就是需要拿到左右两边的叉子才进餐</strong>；</li>
<li><strong>吃完后，会把两支叉子放回原处，继续思考</strong>；</li>
</ul>
<p>那么问题来了，如何保证哲 学家们的动作有序进行，而不会出现有人永远拿不到叉子呢？</p>
<blockquote>
<p>方案一</p>
</blockquote>
<p>我们用信号量的方式，也就是 PV 操作来尝试解决它，代码如下：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/24-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%80%E7%A4%BA%E4%BE%8B.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/24-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%80%E7%A4%BA%E4%BE%8B.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/24-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%80%E7%A4%BA%E4%BE%8B.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/24-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%80%E7%A4%BA%E4%BE%8B.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/24-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%80%E7%A4%BA%E4%BE%8B.jpg"
        title="img" /></p>
<p>上面的程序，好似很自然。拿起叉子用 P 操作，代表有叉子就直接用，没有叉子时就等待其他哲学家放回叉子。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/25-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%80%E9%97%AE%E9%A2%98.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/25-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%80%E9%97%AE%E9%A2%98.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/25-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%80%E9%97%AE%E9%A2%98.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/25-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%80%E9%97%AE%E9%A2%98.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/25-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%80%E9%97%AE%E9%A2%98.jpg"
        title="方案一的问题" /></p>
<p>不过，这种解法存在一个极端的问题：<strong>假设五位哲学家同时拿起左边的叉子，桌面上就没有叉子了， 这样就没有人能够拿到他们右边的叉子，也就说每一位哲学家都会在 <code>P(fork[(i + 1) % N ])</code> 这条语句阻塞了，很明显这发生了死锁的现象</strong>。</p>
<blockquote>
<p>方案二</p>
</blockquote>
<p>既然「方案一」会发生同时竞争左边叉子导致死锁的现象，那么我们就在拿叉子前，加个互斥信号量，代码如下：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/26-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%BA%8C%E7%A4%BA%E4%BE%8B.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/26-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%BA%8C%E7%A4%BA%E4%BE%8B.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/26-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%BA%8C%E7%A4%BA%E4%BE%8B.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/26-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%BA%8C%E7%A4%BA%E4%BE%8B.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/26-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%BA%8C%E7%A4%BA%E4%BE%8B.jpg"
        title="img" /></p>
<p>上面程序中的互斥信号量的作用就在于，<strong>只要有一个哲学家进入了「临界区」，也就是准备要拿叉子时，其他哲学家都不能动，只有这位哲学家用完叉子了，才能轮到下一个哲学家进餐。</strong></p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/27-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%BA%8C%E9%97%AE%E9%A2%98.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/27-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%BA%8C%E9%97%AE%E9%A2%98.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/27-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%BA%8C%E9%97%AE%E9%A2%98.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/27-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%BA%8C%E9%97%AE%E9%A2%98.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/27-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%BA%8C%E9%97%AE%E9%A2%98.jpg"
        title="方案二的问题" /></p>
<p>方案二虽然能让哲学家们按顺序吃饭，但是每次进餐只能有一位哲学家，而桌面上是有 5 把叉子，按道理是能可以有两个哲学家同时进餐的，所以从效率角度上，这不是最好的解决方案。</p>
<blockquote>
<p>方案三</p>
</blockquote>
<p>那既然方案二使用互斥信号量，会导致只能允许一个哲学家就餐，那么我们就不用它。</p>
<p>另外，方案一的问题在于，会出现所有哲学家同时拿左边刀叉的可能性，那我们就避免哲学家可以同时拿左边的刀叉，采用分支结构，根据哲学家的编号的不同，而采取不同的动作。</p>
<p><strong>即让偶数编号的哲学家「先拿左边的叉子后拿右边的叉子」，奇数编号的哲学家「先拿右边的叉子后拿左边的叉子」。</strong></p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/28-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%89%E7%A4%BA%E4%BE%8B.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/28-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%89%E7%A4%BA%E4%BE%8B.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/28-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%89%E7%A4%BA%E4%BE%8B.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/28-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%89%E7%A4%BA%E4%BE%8B.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/28-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%89%E7%A4%BA%E4%BE%8B.jpg"
        title="img" /></p>
<p>上面的程序，在 P 操作时，根据哲学家的编号不同，拿起左右两边叉子的顺序不同。另外，V 操作是不需要分支的，因为 V 操作是不会阻塞的。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/29-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%89-%E5%9B%BE%E8%A7%A3.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/29-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%89-%E5%9B%BE%E8%A7%A3.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/29-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%89-%E5%9B%BE%E8%A7%A3.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/29-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%89-%E5%9B%BE%E8%A7%A3.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/29-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E4%B8%89-%E5%9B%BE%E8%A7%A3.jpg"
        title="方案三可解决问题" /></p>
<p>方案三即不会出现死锁，也可以两人同时进餐。</p>
<blockquote>
<p>方案四</p>
</blockquote>
<p>在这里再提出另外一种可行的解决方案，我们<strong>用一个数组 state 来记录每一位哲学家的三个状态，分别是在进餐状态、思考状态、饥饿状态（正在试图拿叉子）。</strong></p>
<p>那么，<strong>一个哲学家只有在两个邻居都没有进餐时，才可以进入进餐状态。</strong></p>
<p>第 <code>i</code> 个哲学家的左邻右舍，则由宏 <code>LEFT</code> 和 <code>RIGHT</code> 定义：</p>
<ul>
<li><em>LEFT</em> : ( i + 5 - 1 ) % 5</li>
<li><em>RIGHT</em> : ( i + 1 ) % 5</li>
</ul>
<p>比如 i 为 2，则 <code>LEFT</code> 为 1，<code>RIGHT</code> 为 3。</p>
<p>具体代码实现如下：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/30-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E5%9B%9B%E7%A4%BA%E4%BE%8B.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/30-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E5%9B%9B%E7%A4%BA%E4%BE%8B.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/30-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E5%9B%9B%E7%A4%BA%E4%BE%8B.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/30-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E5%9B%9B%E7%A4%BA%E4%BE%8B.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/30-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E5%9B%9B%E7%A4%BA%E4%BE%8B.jpg"
        title="img" /></p>
<p>上面的程序使用了一个信号量数组，每个信号量对应一位哲学家，这样在所需的叉子被占用时，想进餐的哲学家就被阻塞。</p>
<p>注意，每个进程/线程将 <code>smart_person</code> 函数作为主代码运行，而其他 <code>take_forks</code>、<code>put_forks</code> 和 <code>test</code> 只是普通的函数，而非单独的进程/线程。</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/31-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E5%9B%9B-%E5%9B%BE%E8%A7%A3.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/31-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E5%9B%9B-%E5%9B%BE%E8%A7%A3.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/31-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E5%9B%9B-%E5%9B%BE%E8%A7%A3.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/31-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E5%9B%9B-%E5%9B%BE%E8%A7%A3.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/31-%E5%93%B2%E5%AD%A6%E5%AE%B6%E8%BF%9B%E9%A4%90-%E6%96%B9%E6%A1%88%E5%9B%9B-%E5%9B%BE%E8%A7%A3.jpg"
        title="方案四也可解决问题" /></p>
<p>方案四同样不会出现死锁，也可以两人同时进餐。</p>
<h3 id="httpsxiaolincodingcomos4_processmultithread_synchtml读者-写者问题读者-写者问题"><a href="https://xiaolincoding.com/os/4_process/multithread_sync.html#%e8%af%bb%e8%80%85-%e5%86%99%e8%80%85%e9%97%ae%e9%a2%98" target="_blank" rel="noopener noreffer">#</a>读者-写者问题</h3>
<p>前面的「哲学家进餐问题」对于互斥访问有限的竞争问题（如 I/O 设备）一类的建模过程十分有用。</p>
<p>另外，还有个著名的问题是「读者-写者」，它为数据库访问建立了一个模型。</p>
<p>读者只会读取数据，不会修改数据，而写者即可以读也可以修改数据。</p>
<p>读者-写者的问题描述：</p>
<ul>
<li>「读-读」允许：同一时刻，允许多个读者同时读</li>
<li>「读-写」互斥：没有写者时读者才能读，没有读者时写者才能写</li>
<li>「写-写」互斥：没有其他写者时，写者才能写</li>
</ul>
<p>接下来，提出几个解决方案来分析分析。</p>
<blockquote>
<p>方案一</p>
</blockquote>
<p>使用信号量的方式来尝试解决：</p>
<ul>
<li>信号量 <code>wMutex</code>：控制写操作的互斥信号量，初始值为 1 ；</li>
<li>读者计数 <code>rCount</code>：正在进行读操作的读者个数，初始化为 0；</li>
<li>信号量 <code>rCountMutex</code>：控制对 rCount 读者计数器的互斥修改，初始值为 1；</li>
</ul>
<p>接下来看看代码的实现：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/32-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%B8%80%E7%A4%BA%E4%BE%8B.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/32-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%B8%80%E7%A4%BA%E4%BE%8B.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/32-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%B8%80%E7%A4%BA%E4%BE%8B.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/32-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%B8%80%E7%A4%BA%E4%BE%8B.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/32-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%B8%80%E7%A4%BA%E4%BE%8B.jpg"
        title="img" /></p>
<p>上面的这种实现，是读者优先的策略，因为只要有读者正在读的状态，后来的读者都可以直接进入，如果读者持续不断进入，则写者会处于饥饿状态。</p>
<blockquote>
<p>方案二</p>
</blockquote>
<p>那既然有读者优先策略，自然也有写者优先策略：</p>
<ul>
<li>只要有写者准备要写入，写者应尽快执行写操作，后来的读者就必须阻塞；</li>
<li>如果有写者持续不断写入，则读者就处于饥饿；</li>
</ul>
<p>在方案一的基础上新增如下变量：</p>
<ul>
<li>信号量 <code>rMutex</code>：控制读者进入的互斥信号量，初始值为 1；</li>
<li>信号量 <code>wDataMutex</code>：控制写者写操作的互斥信号量，初始值为 1；</li>
<li>写者计数 <code>wCount</code>：记录写者数量，初始值为 0；</li>
<li>信号量 <code>wCountMutex</code>：控制 wCount 互斥修改，初始值为 1；</li>
</ul>
<p>具体实现如下代码：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/33-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%BA%8C%E7%A4%BA%E4%BE%8B.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/33-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%BA%8C%E7%A4%BA%E4%BE%8B.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/33-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%BA%8C%E7%A4%BA%E4%BE%8B.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/33-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%BA%8C%E7%A4%BA%E4%BE%8B.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/33-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%BA%8C%E7%A4%BA%E4%BE%8B.jpg"
        title="img" /></p>
<p>注意，这里 <code>rMutex</code> 的作用，开始有多个读者读数据，它们全部进入读者队列，此时来了一个写者，执行了 <code>P(rMutex)</code> 之后，后续的读者由于阻塞在 <code>rMutex</code> 上，都不能再进入读者队列，而写者到来，则可以全部进入写者队列，因此保证了写者优先。</p>
<p>同时，第一个写者执行了 <code>P(rMutex)</code> 之后，也不能马上开始写，必须等到所有进入读者队列的读者都执行完读操作，通过 <code>V(wDataMutex)</code> 唤醒写者的写操作。</p>
<blockquote>
<p>方案三</p>
</blockquote>
<p>既然读者优先策略和写者优先策略都会造成饥饿的现象，那么我们就来实现一下公平策略。</p>
<p>公平策略：</p>
<ul>
<li>优先级相同；</li>
<li>写者、读者互斥访问；</li>
<li>只能一个写者访问临界区；</li>
<li>可以有多个读者同时访问临界资源；</li>
</ul>
<p>具体代码实现：</p>
<p><img
        class="lazyload"
        src="/svg/loading.min.svg"
        data-src="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/34-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%B8%89%E7%A4%BA%E4%BE%8B.jpg"
        data-srcset="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/34-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%B8%89%E7%A4%BA%E4%BE%8B.jpg, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/34-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%B8%89%E7%A4%BA%E4%BE%8B.jpg 1.5x, https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/34-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%B8%89%E7%A4%BA%E4%BE%8B.jpg 2x"
        data-sizes="auto"
        alt="https://cdn.xiaolincoding.com/gh/xiaolincoder/ImageHost/%E6%93%8D%E4%BD%9C%E7%B3%BB%E7%BB%9F/%E4%BA%92%E6%96%A5%E4%B8%8E%E5%90%8C%E6%AD%A5/34-%E8%AF%BB%E8%80%85%E5%86%99%E8%80%85-%E6%96%B9%E6%A1%88%E4%B8%89%E7%A4%BA%E4%BE%8B.jpg"
        title="img" /></p>
<p>看完代码不知你是否有这样的疑问，为什么加了一个信号量 <code>flag</code>，就实现了公平竞争？</p>
<p>对比方案一的读者优先策略，可以发现，读者优先中只要后续有读者到达，读者就可以进入读者队列， 而写者必须等待，直到没有读者到达。</p>
<p>没有读者到达会导致读者队列为空，即 <code>rCount==0</code>，此时写者才可以进入临界区执行写操作。</p>
<p>而这里 <code>flag</code> 的作用就是阻止读者的这种特殊权限（特殊权限是只要读者到达，就可以进入读者队列）。</p>
<p>比如：开始来了一些读者读数据，它们全部进入读者队列，此时来了一个写者，执行 <code>P(falg)</code> 操作，使得后续到来的读者都阻塞在 <code>flag</code> 上，不能进入读者队列，这会使得读者队列逐渐为空，即 <code>rCount</code> 减为 0。</p>
<p>这个写者也不能立马开始写（因为此时读者队列不为空），会阻塞在信号量 <code>wDataMutex</code> 上，读者队列中的读者全部读取结束后，最后一个读者进程执行 <code>V(wDataMutex)</code>，唤醒刚才的写者，写者则继续开始进行写操作。</p>
</div><div class="post-footer" id="post-footer">
    <div class="post-info"><div class="post-info-tag"><span><a href="/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%B2%E7%AA%81%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E/">多线程冲突了怎么办</a>
                </span></div><div class="post-info-line"><div class="post-info-mod">
                <span>更新于 2023-05-27</span>
            </div><div class="post-info-mod"></div>
        </div><div class="post-info-share">
            <span><a href="javascript:void(0);" title="分享到 Twitter" data-sharer="twitter" data-url="https://acking-you.github.io/posts/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%B2%E7%AA%81%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E/" data-title="二、多线程冲突了怎么办" data-hashtags="多线程冲突了怎么办"><i class="fab fa-twitter fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Facebook" data-sharer="facebook" data-url="https://acking-you.github.io/posts/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%B2%E7%AA%81%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E/" data-hashtag="多线程冲突了怎么办"><i class="fab fa-facebook-square fa-fw"></i></a><a href="javascript:void(0);" title="分享到 WhatsApp" data-sharer="whatsapp" data-url="https://acking-you.github.io/posts/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%B2%E7%AA%81%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E/" data-title="二、多线程冲突了怎么办" data-web><i class="fab fa-whatsapp fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Line" data-sharer="line" data-url="https://acking-you.github.io/posts/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%B2%E7%AA%81%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E/" data-title="二、多线程冲突了怎么办"><i class="fab fa-line fa-fw"></i></a><a href="javascript:void(0);" title="分享到 微博" data-sharer="weibo" data-url="https://acking-you.github.io/posts/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%B2%E7%AA%81%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E/" data-title="二、多线程冲突了怎么办" data-image="https://img-blog.csdnimg.cn/img_convert/c592952aec4e1c875073f2a3d970cde5.png#pic_center"><i class="fab fa-weibo fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Myspace" data-sharer="myspace" data-url="https://acking-you.github.io/posts/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%B2%E7%AA%81%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E/" data-title="二、多线程冲突了怎么办" data-description="多线程冲突了怎么办"><i data-svg-src="/lib/simple-icons/icons/myspace.min.svg"></i></a><a href="javascript:void(0);" title="分享到 Blogger" data-sharer="blogger" data-url="https://acking-you.github.io/posts/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%B2%E7%AA%81%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E/" data-title="二、多线程冲突了怎么办" data-description="多线程冲突了怎么办"><i class="fab fa-blogger fa-fw"></i></a><a href="javascript:void(0);" title="分享到 Evernote" data-sharer="evernote" data-url="https://acking-you.github.io/posts/%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%86%B2%E7%AA%81%E4%BA%86%E6%80%8E%E4%B9%88%E5%8A%9E/" data-title="二、多线程冲突了怎么办"><i class="fab fa-evernote fa-fw"></i></a></span>
        </div></div><div class="post-nav"><a href="/posts/lab0-%E5%AE%9E%E7%8E%B0bytestream/" class="prev" rel="prev" title="lab0-实现ByteStream"><i class="fas fa-angle-left fa-fw"></i>Previous Post</a>
            <a href="/posts/%E4%BA%8Cmysql%E4%B8%80%E6%9D%A1%E8%AE%B0%E5%BD%95%E6%80%8E%E4%B9%88%E5%AD%98%E5%82%A8%E7%9A%84/" class="next" rel="next" title="二、MySQL一条记录怎么存储的？">Next Post<i class="fas fa-angle-right fa-fw"></i></a></div></div>
</div></article></div>
            </main>
            <footer class="footer"><div class="footer-container"><div class="footer-line">由 <a href="https://gohugo.io/" target="_blank" rel="noopener noreffer" title="Hugo 0.86.0">Hugo</a> 强力驱动 | 主题 - <a href="https://github.com/khusika/FeelIt" target="_blank" rel="noopener noreffer" title="FeelIt 1.0.1"><i class="fas fa-hand-holding-heart fa-fw"></i> FeelIt</a>
        </div><div class="footer-line" itemscope itemtype="http://schema.org/CreativeWork"><i class="far fa-copyright fa-fw"></i><span itemprop="copyrightYear">2023</span><span class="author" itemprop="copyrightHolder">&nbsp;<a href="/"></a></span></div>
</div>
</footer>
        </div>

        <div id="fixed-buttons"><a href="#" id="back-to-top" class="fixed-button" title="回到顶部">
                <i class="fas fa-chevron-up fa-fw"></i>
            </a></div><link rel="stylesheet" href="/lib/fontawesome-free/all.min.css"><link rel="stylesheet" href="/lib/animate/animate.min.css"><link rel="stylesheet" href="/lib/katex/katex.min.css"><link rel="stylesheet" href="/lib/katex/copy-tex.min.css"><script src="/lib/autocomplete/autocomplete.min.js"></script><script src="/lib/lunr/lunr.min.js"></script><script src="/lib/lunr/lunr.stemmer.support.min.js"></script><script src="/lib/lunr/lunr.zh.min.js"></script><script src="/lib/lazysizes/lazysizes.min.js"></script><script src="/lib/clipboard/clipboard.min.js"></script><script src="/lib/sharer/sharer.min.js"></script><script src="/lib/katex/katex.min.js"></script><script src="/lib/katex/auto-render.min.js"></script><script src="/lib/katex/copy-tex.min.js"></script><script src="/lib/katex/mhchem.min.js"></script><script>window.config={"code":{"copyTitle":"复制到剪贴板","maxShownLines":200},"comment":{},"math":{"delimiters":[{"display":true,"left":"$$","right":"$$"},{"display":true,"left":"\\[","right":"\\]"},{"display":false,"left":"$","right":"$"},{"display":false,"left":"\\(","right":"\\)"}],"strict":false},"search":{"highlightTag":"em","lunrIndexURL":"/index.json","lunrLanguageCode":"zh","lunrSegmentitURL":"/lib/lunr/lunr.segmentit.js","maxResultLength":100,"noResultsFound":"没有找到结果","snippetLength":50,"type":"lunr"}};</script><script src="/js/theme.min.js"></script></body></html>
